package cn.song.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.song.controller.common.CommonService;
import cn.song.entity.easyexcel.ExcelModel;
import cn.song.entity.excel.ExcelDataModel;
import cn.song.model.DownloadDto;
import cn.song.model.R;
import cn.song.service.ExcelService;
import cn.song.util.RedisUtil;
import cn.song.util.easyexcel.DownloadUtil;
import cn.song.util.easyexcel.EnhancedExcelExporter;
import com.alibaba.excel.EasyExcel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.*;

import static cn.hutool.json.JSONUtil.toJsonStr;


/**
 * Excel操作核心控制器
 *
 * <p>本控制器是Excel处理功能的核心入口，提供完整的Excel文件操作API接口，
 * 支持多种Excel操作库的统一访问和管理。具备完善的异步处理、任务管理、错误处理等企业级功能。</p>
 *
 * <p>主要功能模块：</p>
 * <ul>
 *   <li>Excel文件上传与解析 - 支持.xlsx和.xls格式，提供数据验证和错误处理</li>
 *   <li>Excel文件下载与导出 - 支持模板下载、数据导出、异步大文件导出</li>
 *   <li>多库支持与切换 - 集成EasyExcel、EasyPoi、Hutool三种Excel操作库</li>
 *   <li>异步任务管理 - 提供任务创建、状态查询、进度跟踪、取消操作等功能</li>
 *   <li>合并单元格导出 - 使用EnhancedExcelExporter实现复杂表格结构导出</li>
 * </ul>
 *
 * <p>支持的Excel操作库：</p>
 * <ul>
 *   <li>EasyExcel 3.3.2 - 阿里巴巴开源，高性能、低内存占用，适合大数据量处理</li>
 *   <li>EasyPoi 4.5.0 - 基于Apache POI封装，功能丰富，支持复杂格式</li>
 *   <li>Hutool 5.8.32 - 国产工具库，简单易用，适合轻量级应用</li>
 * </ul>
 *
 * <p>技术特性：</p>
 * <ul>
 *   <li>Redis缓存 - 用于任务状态管理和防重复提交</li>
 *   <li>线程池异步处理 - 支持大文件异步导入导出</li>
 *   <li>统一异常处理 - 提供友好的错误提示和日志记录</li>
 *   <li>任务进度跟踪 - 实时显示处理进度和状态</li>
 *   <li>文件安全检查 - 文件类型验证、大小限制等安全措施</li>
 * </ul>
 *
 * @author SongRenShuo
 * @version 1.0
 * @since 2025-01-21
 * @see cn.song.service.ExcelService Excel操作服务接口
 * @see cn.song.util.easyexcel.DownloadUtil 下载任务管理工具
 * @see cn.song.util.RedisUtil Redis缓存工具
 * @see cn.song.util.easyexcel.EnhancedExcelExporter 增强Excel导出器
 */
@Slf4j
@RestController
@RequestMapping("/api/excel")
public class ExcelController {

    /**
     * Redis缓存操作工具，用于任务状态管理和防重复提交控制
     */

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 下载任务管理工具，提供异步任务的创建、查询、状态更新等功能
     */

    @Autowired
    private DownloadUtil downloadUtil;

    /**
     * EasyExcel操作服务实现，阿里巴巴开源的高性能Excel处理库
     */

    @Autowired
    @Qualifier("easyExcelService")
    private ExcelService easyExcelService;

    /**
     * EasyPoi操作服务实现，基于Apache POI的功能丰富的Excel处理库
     */

    @Autowired
    @Qualifier("easyPoiService")
    private ExcelService easyPoiService;

    /**
     * Hutool Excel操作服务实现，国产工具库的简单易用Excel处理方案
     */

    @Autowired
    @Qualifier("hutoolExcelService")
    private ExcelService hutoolExcelService;

    /**
     * 公共业务服务，提供账户信息获取、通用下载等功能
     */

    @Autowired
    private CommonService commonService;


    /**
     * 异步任务执行器，用于执行耗时的Excel处理任务
     */
    @Autowired
    private TaskExecutor asyncExecutor;

    /**
     * Excel文件上传与数据解析接口
     *
     * <p>此方法接收上传的Excel文件，根据指定的库类型进行解析，
     * 并返回结构化的数据列表。支持多种Excel格式和库的统一访问。</p>
     *
     * <p>处理流程：</p>
     * <ol>
     *   <li>Ⅰ. 根据type参数选择对应的Excel处理服务</li>
     *   <li>Ⅱ. 验证文件类型和大小</li>
     *   <li>Ⅲ. 调用对应服务的uploadExcel方法解析数据</li>
     *   <li>Ⅳ. 返回统一格式的响应结果</li>
     * </ol>
     *
     * @param file 上传的Excel文件，支持.xlsx和.xls格式
     * @param type Excel库类型，支持值：easyexcel(默认)/easypoi/hutool
     * @return 包含解析数据的响应对象，数据类型为ExcelDataModel列表
     * @since 1.0
     * @apiNote 默认使用EasyExcel库，建议大数据量处理时使用
     */
    @PostMapping("/upload")
    public R<List<ExcelDataModel>> uploadExcel(@RequestParam("file") MultipartFile file,
                                               @RequestParam(value = "type", defaultValue = "easyexcel") String type) {
        try {
            // Ⅰ. 根据type参数选择对应的Excel处理服务实现
            ExcelService excelService = getExcelService(type);
            log.debug("开始上传Excel文件，使用库类型: {}, 文件名: {}, 文件大小: {} bytes",
                    type, file.getOriginalFilename(), file.getSize());

            // Ⅱ. 基本文件验证（非空、格式、大小等）
            if (file.isEmpty()) {
                return R.failed("上传文件不能为空");
            }

            // Ⅲ. 调用对应服务的uploadExcel方法进行数据解析
            List<ExcelDataModel> dataList = excelService.uploadExcel(file);

            log.info("成功上传并解析Excel文件，库类型: {}, 解析数据行数: {}", type, dataList.size());

            // Ⅳ. 返回统一格式的成功响应
            return R.success(dataList);
        } catch (Exception e) {
            log.error("上传Excel文件失败，库类型: {}, 文件名: {}", type, file.getOriginalFilename(), e);
            return R.failed("上传失败：" + e.getMessage());
        }
    }

    /**
     * Excel文件下载接口
     *
     * <p>根据指定的Excel库类型生成示例数据并导出为Excel文件，
     * 直接通过HTTP响应流输出给客户端进行下载。</p>
     *
     * <p>下载流程：</p>
     * <ol>
     *   <li>Ⅰ. 根据type参数获取对应的Excel处理服务</li>
     *   <li>Ⅱ. 生成示例数据集合</li>
     *   <li>Ⅲ. 设置响应头和文件名</li>
     *   <li>Ⅳ. 调用服务将数据导出为Excel文件</li>
     * </ol>
     *
     * @param type Excel库类型，支持值：easyexcel(默认)/easypoi/hutool
     * @param response HTTP响应对象，用于输出Excel文件数据流
     * @since 1.0
     * @apiNote 生成的示例数据包括3行测试用户信息，用于功能演示
     */
    @GetMapping("/download")
    public void downloadExcel(@RequestParam(value = "type", defaultValue = "easyexcel") String type,
                              HttpServletResponse response) {
        try {
            // Ⅰ. 根据type参数获取对应的Excel处理服务实现
            ExcelService excelService = getExcelService(type);

            // Ⅱ. 生成示例数据集合（包含3行测试数据）
            List<ExcelDataModel> dataList = generateSampleData();

            // Ⅲ. 构建下载文件名（包含库类型标识）
            String fileName = type + "_data.xlsx";

            log.info("开始下载Excel文件，库类型: {}, 文件名: {}, 数据行数: {}",
                    type, fileName, dataList.size());

            // Ⅳ. 调用对应服务将数据导出为Excel文件并输出到响应流
            excelService.downloadExcel(dataList, response, fileName);

            log.info("成功完成Excel文件下载，库类型: {}, 文件名: {}", type, fileName);
        } catch (Exception e) {
            log.error("下载Excel文件失败，库类型: {}", type, e);
            // 尝试向响应体写入错误信息
            try {
                if (!response.isCommitted()) {
                    response.setStatus(500);
                    response.setContentType("text/plain; charset=utf-8");
                    response.getWriter().write("下载失败: " + e.getMessage());
                }
            } catch (IOException ioException) {
                log.error("写入错误响应失败", ioException);
            }
        }
    }

    /**
     * Excel模板文件下载接口
     *
     * <p>根据指定的Excel库类型生成对应的空模板文件，
     * 用于用户下载后填入数据再上传。模板包含正确的列标题和格式。</p>
     *
     * <p>模板生成流程：</p>
     * <ol>
     *   <li>Ⅰ. 根据type参数获取对应的Excel处理服务</li>
     *   <li>Ⅱ. 构建模板文件名</li>
     *   <li>Ⅲ. 调用服务生成空模板并输出</li>
     * </ol>
     *
     * @param type Excel库类型，支持值：easyexcel(默认)/easypoi/hutool
     * @param response HTTP响应对象，用于输出模板文件数据流
     * @since 1.0
     * @apiNote 模板文件包含正确的列标题定义，但不包含数据内容
     */
    @GetMapping("/template")
    public void downloadTemplate(@RequestParam(value = "type", defaultValue = "easyexcel") String type,
                                 HttpServletResponse response) {
        try {
            // Ⅰ. 根据type参数获取对应的Excel处理服务实现
            ExcelService excelService = getExcelService(type);

            // Ⅱ. 构建模板文件名（包含库类型标识）
            String fileName = type + "_template.xlsx";

            log.info("开始下载Excel模板文件，库类型: {}, 文件名: {}", type, fileName);

            // Ⅲ. 调用对应服务生成空模板并输出到响应流
            excelService.downloadTemplate(response, fileName);

            log.info("成功完成Excel模板文件下载，库类型: {}, 文件名: {}", type, fileName);
        } catch (Exception e) {
            log.error("下载Excel模板文件失败，库类型: {}", type, e);
            // 尝试向响应体写入错误信息
            try {
                if (!response.isCommitted()) {
                    response.setStatus(500);
                    response.setContentType("text/plain; charset=utf-8");
                    response.getWriter().write("模板下载失败: " + e.getMessage());
                }
            } catch (IOException ioException) {
                log.error("写入错误响应失败", ioException);
            }
        }
    }

    /**
     * Excel文件异步处理接口
     *
     * <p>此方法用于处理大型Excel文件的异步解析任务，避免长时间阻塞请求。
     * 支持任务状态跟踪、进度查询和防重复提交机制。</p>
     *
     * <p>异步处理流程：</p>
     * <ol>
     *   <li>Ⅰ. 获取请求者账户信息</li>
     *   <li>Ⅱ. 检查是否存在未完成的相同任务（防重复提交）</li>
     *   <li>Ⅲ. 创建下载任务对象并设置初始状态</li>
     *   <li>Ⅳ. 调用对应服务的异步处理方法</li>
     *   <li>Ⅴ. 返回任务ID供客户端进行状态查询</li>
     * </ol>
     *
     * @param file 待处理的Excel文件，支持大文件上传
     * @param type Excel库类型，支持值：easyexcel(默认)/easypoi/hutool
     * @param request HTTP请求对象，用于获取账户信息
     * @return 包含任务ID的响应对象，可用于后续状态查询
     * @since 1.0
     * @apiNote 使用Redis进行任务锁管理，防止同一账户重复提交任务
     */
    @PostMapping("/batch")
    public R<String> processExcelAsync(@RequestParam("file") MultipartFile file,
                                       @RequestParam(value = "type", defaultValue = "easyexcel") String type,
                                       HttpServletRequest request) {
        try {
            // Ⅰ. 根据type参数获取对应的Excel处理服务实现
            ExcelService excelService = getExcelService(type);

            // Ⅱ. 从请求中获取账户信息
            String account = getAccountFromRequest(request);
            String key = "Upload_" + account;

            log.info("开始异步处理Excel文件，账户: {}, 库类型: {}, 文件名: {}, 文件大小: {} bytes",
                    account, type, file.getOriginalFilename(), file.getSize());

            // Ⅲ. 检查是否存在未完成的导入任务（使用Redis分布式锁防重复提交）
            // 30分钟过期
            Boolean isExist = redisUtil.setIfAbsent(key, 0, 60 * 30);
            if (!isExist) {
                log.warn("账户 {} 存在未完成的导入任务，拒绝新任务", account);
                return R.failed("当前存在未完成的导入任务，请稍后再试");
            }

            // Ⅳ. 创建下载任务对象并设置初始状态
            DownloadDto downloadDto = DownloadDto.builder()
                    // 设置任务所属账户
                    .zhangHu(account)
                    // 任务显示名称
                    .fileName("Excel数据处理")
                    // 任务类型（Excel处理）
                    .model(4)
                    // 初始状态：导出中
                    .exportStatus(2)
                    // 状态描述
                    .description("数据处理中，请耐心等待")
                    .build();

            // Ⅴ. 调用对应服务的异步处理方法并获取任务ID
            String taskId = excelService.processExcelAsync(file, downloadDto);

            log.info("成功创建异步Excel处理任务，账户: {}, 任务ID: {}", account, taskId);

            // Ⅵ. 返回任务ID供客户端进行状态查询
            return R.success(taskId);

        } catch (Exception e) {
            log.error("异步处理Excel文件失败", e);
            return R.failed("处理失败：" + e.getMessage());
        }
    }

    /**
     * 异步导出Excel文件接口
     *
     * <p>此接口用于发起异步Excel数据导出任务，支持大数据量导出而不阻塞请求响应。
     * 采用统一的任务管理机制，提供完整的任务生命周期跟踪和状态查询能力。</p>
     *
     * <p>功能特性：</p>
     * <ul>
     *   <li>防重复提交 - 使用Redis分布式锁防止同一账户重复提交导出任务</li>
     *   <li>统一任务管理 - 集成DownloadUtil进行任务生命周期管理</li>
     *   <li>多库支持 - 支持EasyExcel、EasyPoi、Hutool三种Excel库</li>
     *   <li>进度跟踪 - 实时更新任务状态和处理进度</li>
     *   <li>文件下载 - 生成可下载的文件链接，支持客户端直接下载</li>
     * </ul>
     *
     * <p>处理流程：</p>
     * <ol>
     *   <li>Ⅰ. 验证账户信息并检查防重复提交锁</li>
     *   <li>Ⅱ. 创建导出任务并保存到任务管理系统</li>
     *   <li>Ⅲ. 异步执行数据查询和Excel文件生成</li>
     *   <li>Ⅳ. 实时更新任务状态和进度</li>
     *   <li>Ⅴ. 完成后提供文件下载链接并释放锁</li>
     * </ol>
     *
     * @param type Excel库类型，支持值：easyexcel(默认)/easypoi/hutool
     * @param request HTTP请求对象，用于获取账户信息
     * @return 包含任务ID的响应对象，用于后续状态查询和文件下载
     * @since 1.0
     * @apiNote 返回的任务ID可用于查询任务状态(/task/status/{taskId})和获取任务列表(/task/list)
     */
    @PostMapping("/export")
    public R<String> exportExcel(@RequestParam(value = "type", defaultValue = "easyexcel") String type,
                                 HttpServletRequest request) {
        try {
            // Ⅰ. 根据type参数获取对应的Excel处理服务实现
            ExcelService excelService = getExcelService(type);

            // Ⅱ. 从请求中获取账户信息
            String account = getAccountFromRequest(request);
            String lockKey = "Export_" + account;

            log.info("开始异步导出Excel文件，账户: {}, 库类型: {}", account, type);

            // Ⅲ. 检查是否存在未完成的导出任务（使用Redis分布式锁防重复提交）
            // 20分钟过期时间，足够完成大部分导出任务
            Boolean lockAcquired = redisUtil.setIfAbsent(lockKey, 0, 60 * 20);
            if (!lockAcquired) {
                log.warn("账户 {} 存在未完成的导出任务，拒绝新的导出请求", account);
                return R.failed("当前存在未完成的导出任务，请稍后再试");
            }

            // Ⅳ. 创建导出任务对象并设置初始状态
            DownloadDto downloadDto = DownloadDto.builder()
                    // 设置任务所属账户
                    .zhangHu(account)
                    // 构建有意义的文件名，包含库类型和时间戳
                    .fileName(type + "_export_" + DateUtil.format(new Date(), "yyyyMMdd_HHmmss") + ".xlsx")
                    // 任务类型（4-其他类型Excel导出）
                    .model(4)
                    // 初始状态：队列中
                    .exportStatus(1)
                    // 状态描述
                    .description("导出任务已创建，等待处理")
                    .build();

            // Ⅴ. 使用DownloadUtil统一管理任务，自动生成UUID和创建时间
            R<String> taskResult = downloadUtil.updateDownloadList(downloadDto);
            if (!taskResult.isCode()) {
                // 创建任务失败，释放锁
                redisUtil.del(lockKey);
                return R.failed("创建导出任务失败：" + taskResult.getMsg());
            }

            String taskId = downloadDto.getUuid();

            // Ⅵ. 同时保存任务到专用的状态查询键中（保持与查询接口兼容）
            String statusKey = "exportInBatches_" + taskId;
            // 24小时过期
            redisUtil.setObject(statusKey, downloadDto, 60 * 60 * 24);

            // Ⅶ. 提交异步导出任务到线程池
            asyncExecutor.execute(() -> {
                try {
                    log.info("开始执行异步导出任务，任务ID: {}, 账户: {}", taskId, account);

                    // 1. 更新任务状态为导出中
                    downloadDto.setExportStatus(2);
                    downloadDto.setDescription("正在查询数据...");
                    downloadDto.setProgress(20);
                    updateTaskStatus(downloadDto, statusKey);

                    // 2. 获取导出数据 - 这里可以根据实际业务需求获取真实数据
                    // TODO: 后续可以添加参数支持，允许前端传递查询条件
                    List<ExcelDataModel> dataList = getExportData(account, type);
                    downloadDto.setTotal(dataList.size());
                    downloadDto.setDescription("数据查询完成，开始生成Excel文件...");
                    downloadDto.setProgress(50);
                    updateTaskStatus(downloadDto, statusKey);

                    // 3. 生成Excel文件到安全的下载目录
                    String fileName = downloadDto.getFileName();
                    String filePath = generateExcelFile(dataList, fileName, type, excelService);

                    // 4. 更新任务状态为完成
                    downloadDto.setExportStatus(3);
                    downloadDto.setDescription("导出完成，文件已生成");
                    downloadDto.setEndDate(new Date());
                    downloadDto.setProgress(100);
                    // 设置相对路径，用于文件下载
                    downloadDto.setDownloadUrl("/api/excel/file/download/" + taskId);
                    updateTaskStatus(downloadDto, statusKey);

                    log.info("Excel异步导出任务完成，任务ID: {}, 文件路径: {}", taskId, filePath);

                } catch (Exception e) {
                    log.error("Excel异步导出任务执行失败，任务ID: {}", taskId, e);

                    // 更新任务状态为异常
                    downloadDto.setExportStatus(4);
                    downloadDto.setDescription("导出失败：" +
                        (StrUtil.length(e.getMessage()) < 100 ? e.getMessage() : "系统内部错误，请联系管理员"));
                    downloadDto.setEndDate(new Date());
                    updateTaskStatus(downloadDto, statusKey);
                } finally {
                    // 无论成功失败都要释放防重复提交锁
                    redisUtil.del(lockKey);
                    log.info("已释放导出锁，账户: {}", account);
                }
            });

            log.info("异步导出任务已提交，任务ID: {}, 账户: {}", taskId, account);
            return R.success("导出任务已创建，请稍后查询任务状态", taskId);

        } catch (Exception e) {
            log.error("创建异步导出任务失败", e);
            return R.failed("导出失败：" + e.getMessage());
        }
    }



    /**
     * 导出 - 异步 - 导出任务列表
     *
     * @date 2025/07/27
     * @author SongRenShuo
     * @return {@link R }<{@link String }>
     */
    @PostMapping("/downloads")
    public R<String> downloads(HttpServletRequest request, ExcelModel dto) {

        String zhangHu = commonService.getZhangHu(null, request);
        String key = "Download_" + zhangHu;
        String taskId = null;
        try {
            Boolean isExist = redisUtil.setIfAbsent(key, 0, 60 * 20);
            if (!isExist) {
                log.error("文件下载 - 账户列表: 导出订单详情 频率过高,账户：{}", zhangHu);
                return R.failed("当前存在未完成的导出任务，请稍后再试");
            }

            String jsonStr = toJsonStr(dto);
            log.info("文件下载 - 账户列表 - ,账户：{} 入参: {} ", zhangHu, jsonStr);


            DownloadDto downloadDto = DownloadDto.builder()
                    .zhangHu(zhangHu)
                    .fileName("账户列表")
                    .model(4).build();
            R<String> taskResult = downloadUtil.updateDownloadList(downloadDto);
            if (!taskResult.isCode()) {
                redisUtil.del(key);
                return R.failed("创建导出任务失败：" + taskResult.getMsg());
            }
            
            taskId = downloadDto.getUuid();


            // 线程池异步导出 - 后期改为队列
            asyncExecutor.execute(() -> {
                // 忽略的字段
                List<String> ignoreParamList = Collections.emptyList();
                commonService.downloads(dto, ignoreParamList, downloadDto);
            });
        } catch (Exception e) {
            log.error("文件下载 账户列表：导出错误：", e);
            redisUtil.del(key);
            String description = StrUtil.format("导出失败:{}", StrUtil.length(e.getMessage()) < 100 ? e.getMessage() : "未知错误，多次失败请联系管理员");
            return R.failed(description);
        }

        return R.success("下载任务创建成功,请稍后查询任务状态", taskId);
    }


    /**
     * 获取任务列表
     *
     * @param model   模型类型
     * @param request HTTP请求
     * @return 任务列表
     */
    @GetMapping("/task/list")
    public R<List<DownloadDto>> getTaskList(@RequestParam(required = false) Integer model,
                                            HttpServletRequest request) {
        try {
            String account = getAccountFromRequest(request);
            List<DownloadDto> taskList = downloadUtil.getDownloadList(account, model);
            return R.success(taskList);
        } catch (Exception e) {
            log.error("获取任务列表失败", e);
            return R.failed("获取任务列表失败：" + e.getMessage());
        }
    }

    /**
     * 取消任务
     *
     * @param taskId 任务ID
     * @param model 模块类型（可选，用于优化查找）
     * @param request HTTP请求对象，用于获取账户信息
     * @return 操作结果
     */
    @PostMapping("/task/cancel/{taskId}")
    public R<String> cancelTask(@PathVariable String taskId, 
                               @RequestParam(required = false) Integer model,
                               HttpServletRequest request) {
        try {
            String account = getAccountFromRequest(request);
            log.info("开始取消任务，任务ID: {}, 模块: {}, 账户: {}", taskId, model, account);
            
            R<String> result;
            if (model != null) {
                // 使用优化的精确查找模式
                result = downloadUtil.terminateDownloadTask(taskId, model, account);
            } else {
                // 使用兼容的遍历查找模式
                result = downloadUtil.terminateDownloadTask(taskId);
            }
            
            if (result.getCode() == 0) {
                log.info("任务取消成功，任务ID: {}, 模块: {}, 账户: {}", taskId, model, account);
            } else {
                log.warn("任务取消失败，任务ID: {}, 原因: {}", taskId, result.getMsg());
            }
            
            return result;
        } catch (Exception e) {
            log.error("取消任务异常，任务ID: {}", taskId, e);
            return R.failed("取消任务失败：" + e.getMessage());
        }
    }

    /**
     * 查询任务状态
     *
     * @param taskId 任务ID
     * @return 任务状态
     */
    @GetMapping("/task/status/{taskId}")
    public R<DownloadDto> getTaskStatus(@PathVariable String taskId) {
        try {
            log.info("开始查询任务状态，任务ID: {}", taskId);
            
            // 同时从两个位置查询任务状态，提供更详细的调试信息
            String statusKey = "exportInBatches_" + taskId;
            DownloadDto downloadDto = redisUtil.getObject(statusKey, DownloadDto.class);
            
            log.info("从状态查询键 {} 查询到的结果: {}", statusKey, downloadDto != null ? "存在" : "不存在");
            
            // 如果状态查询键没有找到，尝试从任务列表中查询
            if (downloadDto == null) {
                log.warn("从状态查询键未找到任务，尝试从任务列表查询。任务ID: {}", taskId);
                
                // 尝试从 DownloadUtil 中获取任务信息
                // 需要遍历所有可能的账户和模型组合，这里提供一个替代方法
                downloadDto = findTaskFromAllSources(taskId);
                
                if (downloadDto != null) {
                    log.info("在任务列表中找到任务，重新同步到状态查询键");
                    // 重新同步到状态查询键
                    redisUtil.setObject(statusKey, downloadDto, 60 * 60 * 24);
                }
            }
            
            if (downloadDto == null) {
                log.warn("任务不存在或已结束，任务ID: {}", taskId);
                return R.failed("任务不存在或已结束");
            }
            
            log.info("查询任务状态成功，任务ID: {}, 状态: {}, 描述: {}", 
                taskId, downloadDto.getExportStatus(), downloadDto.getDescription());
            return R.success(downloadDto);
        } catch (Exception e) {
            log.error("查询任务状态失败，任务ID: {}", taskId, e);
            return R.failed("查询任务状态失败：" + e.getMessage());
        }
    }
    
    /**
     * 调试接口：查询任务的Redis存储情况
     * 
     * @param taskId 任务ID
     * @return Redis存储详细信息
     */
    @GetMapping("/task/debug/{taskId}")
    public R<Map<String, Object>> debugTaskStatus(@PathVariable String taskId) {
        Map<String, Object> debugInfo = new HashMap<>();
        
        try {
            // 检查状态查询键
            String statusKey = "exportInBatches_" + taskId;
            DownloadDto statusDto = redisUtil.getObject(statusKey, DownloadDto.class);
            debugInfo.put("statusKey", statusKey);
            debugInfo.put("statusKeyExists", statusDto != null);
            if (statusDto != null) {
                debugInfo.put("statusData", statusDto);
            }
            
            // 检查防重复提交锁状态
            debugInfo.put("lockKeys", checkLockKeys());
            
            // 尝试从任务列表查询
            DownloadDto listDto = findTaskFromAllSources(taskId);
            debugInfo.put("foundInTaskList", listDto != null);
            if (listDto != null) {
                debugInfo.put("taskListData", listDto);
            }
            
            log.info("任务调试信息，任务ID: {}, 结果: {}", taskId, debugInfo);
            return R.success(debugInfo);
        } catch (Exception e) {
            log.error("获取任务调试信息失败，任务ID: {}", taskId, e);
            debugInfo.put("error", e.getMessage());
            return R.failed("获取调试信息失败：" + e.getMessage());
        }
    }
    
    /**
     * 文件下载接口
     *
     * <p>根据任务ID下载对应的Excel文件。支持安全性校验和文件存在性检查，
     * 确保只有有效的任务才能下载对应文件。</p>
     *
     * <p>安全特性：</p>
     * <ul>
     *   <li>任务权限校验 - 只能下载完成状态的任务文件</li>
     *   <li>文件存在性检查 - 防止下载不存在的文件</li>
     *   <li>路径安全 - 防止路径遭历攻击</li>
     *   <li>文件类型限制 - 只允许下载.xlsx文件</li>
     * </ul>
     *
     * @param taskId 任务ID，用于定位对应的导出文件
     * @param response HTTP响应对象，用于输出文件流
     * @since 1.0
     * @apiNote 该接口仅允许下载状态为"导出完成"(exportStatus=3)的任务文件
     */
    @GetMapping("/file/download/{taskId}")
    public void downloadFile(@PathVariable String taskId, HttpServletResponse response) {
        try {
            log.info("接收文件下载请求，任务ID: {}", taskId);

            // 1. 查询任务状态（支持自动恢复）
            String statusKey = "exportInBatches_" + taskId;
            DownloadDto downloadDto = redisUtil.getObject(statusKey, DownloadDto.class);
            
            // 如果状态查询键没有找到，尝试从任务列表中查询并恢复
            if (downloadDto == null) {
                log.warn("从状态查询键未找到任务，尝试从任务列表查询。任务ID: {}", taskId);
                downloadDto = findTaskFromAllSources(taskId);
                
                if (downloadDto != null) {
                    log.info("在任务列表中找到任务，重新同步到状态查询键");
                    // 重新同步到状态查询键
                    redisUtil.setObject(statusKey, downloadDto, 60 * 60 * 24);
                }
            }

            if (downloadDto == null) {
                log.warn("任务不存在，任务ID: {}", taskId);
                response.setStatus(404);
                response.setContentType("text/plain; charset=utf-8");
                response.getWriter().write("任务不存在或已过期");
                return;
            }

            // 2. 检查任务状态，只允许下载完成的任务
            if (downloadDto.getExportStatus() != 3) {
                log.warn("任务未完成，不能下载，任务ID: {}, 当前状态: {}", taskId, downloadDto.getExportStatus());
                response.setStatus(400);
                response.setContentType("text/plain; charset=utf-8");
                response.getWriter().write("任务尚未完成，无法下载文件");
                return;
            }

            // 3. 构建文件路径（保证路径安全）
            String downloadDir = System.getProperty("user.dir") + "/downloads/";
            String fileName = downloadDto.getFileName();
            String filePath = downloadDir + fileName;

            // 4. 检查文件是否存在
            java.io.File file = new java.io.File(filePath);
            if (!file.exists() || !file.isFile()) {
                log.error("文件不存在，任务ID: {}, 文件路径: {}", taskId, filePath);
                response.setStatus(404);
                response.setContentType("text/plain; charset=utf-8");
                response.getWriter().write("文件不存在或已被删除");
                return;
            }

            // 5. 安全检查：确保文件在预期目录内（防止路径遭历）
            String canonicalFilePath = file.getCanonicalPath();
            String canonicalDownloadDir = new java.io.File(downloadDir).getCanonicalPath();
            if (!canonicalFilePath.startsWith(canonicalDownloadDir)) {
                log.error("非法路径访问尝试，任务ID: {}, 访问路径: {}", taskId, canonicalFilePath);
                response.setStatus(403);
                response.setContentType("text/plain; charset=utf-8");
                response.getWriter().write("非法访问");
                return;
            }

            // 6. 设置响应头，触发浏览器下载
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + encodedFileName);
            response.setHeader("Content-Length", String.valueOf(file.length()));

            // 7. 输出文件内容
            try (java.io.FileInputStream fis = new java.io.FileInputStream(file);
                 java.io.BufferedInputStream bis = new java.io.BufferedInputStream(fis)) {

                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = bis.read(buffer)) != -1) {
                    response.getOutputStream().write(buffer, 0, bytesRead);
                }
                response.getOutputStream().flush();

                log.info("文件下载完成，任务ID: {}, 文件名: {}, 文件大小: {} bytes",
                    taskId, fileName, file.length());
            }

        } catch (Exception e) {
            log.error("文件下载失败，任务ID: {}", taskId, e);
            try {
                if (!response.isCommitted()) {
                    response.setStatus(500);
                    response.setContentType("text/plain; charset=utf-8");
                    response.getWriter().write("下载失败：" + e.getMessage());
                }
            } catch (Exception ex) {
                log.error("写入错误响应失败", ex);
            }
        }
    }

    /**
     * Excel库类型服务选择器
     *
     * <p>此方法根据传入的类型字符串返回对应的Excel操作服务实现。
     * 支持三种不同的Excel库，每种库都有各自的特点和适用场景。</p>
     *
     * <p>支持的库类型：</p>
     * <ul>
     *   <li>easyexcel - 默认选项，阿里巴巴开源，高性能低内存</li>
     *   <li>easypoi - 基于Apache POI，功能丰富，支持复杂格式</li>
     *   <li>hutool - 国产工具库，简单易用，适合轻量级应用</li>
     * </ul>
     *
     * @param type 库类型字符串，不区分大小写
     * @return 对应的ExcelService实现实例
     * @since 1.0
     * @apiNote 无效的type参数将默认返回EasyExcel服务
     */
    private ExcelService getExcelService(String type) {
        // 根据类型字符串（不区分大小写）选择对应的服务实现
        switch (type.toLowerCase()) {
            case "easypoi":
                log.debug("使用EasyPoi库进行Excel操作");
                return easyPoiService;
            case "hutool":
                log.debug("使用Hutool库进行Excel操作");
                return hutoolExcelService;
            case "easyexcel":
            default:
                log.debug("使用EasyExcel库进行Excel操作（默认）");
                return easyExcelService;
        }
    }

    /**
     * 从 HTTP请求中安全获取账户信息
     *
     * <p>此方法通过公共服务从请求中提取账户信息，
     * 支持多种账户信息获取方式（如Header、Session、Token等）。</p>
     *
     * @param request HTTP请求对象，包含用户账户信息
     * @return 账户标识字符串，用于任务管理和权限控制
     * @since 1.0
     * @see CommonService#getZhangHu(String, HttpServletRequest) 具体账户获取实现
     */
    private String getAccountFromRequest(HttpServletRequest request) {
        // 委托给公共服务处理账户信息获取逻辑
        String account = commonService.getZhangHu(null, request);
        log.debug("从请求中获取到账户信息: {}", account);
        return account;
    }

    /**
     * 生成Excel演示用样例数据
     *
     * <p>此方法生成一组测试数据，用于Excel导出功能演示和测试。
     * 数据包含完整的用户信息字段，展示不同数据类型的处理能力。</p>
     *
     * <p>数据包含字段：</p>
     * <ul>
     *   <li>ID - 唯一标识符（UUID格式）</li>
     *   <li>姓名 - 中文姓名示例</li>
     *   <li>生日 - 日期类型字段</li>
     *   <li>年龄 - 数值类型字段</li>
     *   <li>创建时间 - 时间戳字段</li>
     *   <li>更新时间 - 时间戳字段</li>
     * </ul>
     *
     * @return 包含3行测试数据的ExcelDataModel列表
     * @since 1.0
     * @apiNote 数据使用Hutool工具生成，保证数据的多样性和真实性
     */
    private List<ExcelDataModel> generateSampleData() {
        List<ExcelDataModel> dataList = new ArrayList<>();

        dataList.add(ExcelDataModel.builder()
                .id(IdUtil.fastSimpleUUID())
                .name("张三")
                .birthday(DateUtil.date())
                .age(25)
                .createTime(new java.util.Date())
                .updateTime(new java.util.Date())
                .build());

        dataList.add(ExcelDataModel.builder()
                .id(IdUtil.fastSimpleUUID())
                .name("李四")
                .birthday(DateUtil.yesterday())
                .age(30)
                .createTime(new java.util.Date())
                .updateTime(new java.util.Date())
                .build());

        dataList.add(ExcelDataModel.builder()
                .id(IdUtil.fastSimpleUUID())
                .name("王五")
                .birthday(DateUtil.tomorrow())
                .age(28)
                .createTime(new java.util.Date())
                .updateTime(new java.util.Date())
                .build());

        return dataList;
    }

    /**
     * 测试EnhancedExcelExporter合并单元格导出功能
     *
     * @param response HTTP响应
     */
    @GetMapping("/enhanced/export")
    public void testEnhancedExcelExport(HttpServletResponse response) {
        try {
            // 1. 生成测试数据
            List<EnhancedExcelExporter.RefundPackage> refundPackages = generateRefundPackageData();

            // 2. 转换为导出DTO
            List<EnhancedExcelExporter.RefundPackageExportDTO> exportData = convertToExportDTO(refundPackages);

            // 3. 创建合并策略
            EnhancedExcelExporter.MergeStrategy<EnhancedExcelExporter.RefundPackageExportDTO> idBasedMergeStrategy =
                    EnhancedExcelExporter.createIdBasedMergeStrategy(
                            // ID提取器
                            EnhancedExcelExporter.RefundPackageExportDTO::getPackageId,
                            // 每个DTO占一行
                            dto -> 1,
                            // 合并列索引
                            0, 1, 2, 3, 4, 5, 6, 7, 8, 9
                    );

            // 4. 创建导出配置
            EnhancedExcelExporter.ExportConfig<EnhancedExcelExporter.RefundPackageExportDTO> config =
                    new EnhancedExcelExporter.ExportConfig<>(EnhancedExcelExporter.RefundPackageExportDTO.class)
                            .withMergeStrategy(idBasedMergeStrategy);

            // 5. 设置响应头
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            String fileName = "退货管理_合并单元格.xlsx";
            String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + encodedFileName);

            // 6. 执行导出
            EnhancedExcelExporter.export(response.getOutputStream(), "退货管理", exportData, config);

            log.info("EnhancedExcelExporter合并单元格导出完成");
        } catch (Exception e) {
            log.error("EnhancedExcelExporter合并单元格导出失败", e);
            try {
                response.setStatus(500);
                response.getWriter().write("导出失败：" + e.getMessage());
            } catch (Exception ex) {
                log.error("写入错误响应失败", ex);
            }
        }
    }

    /**
     * 生成退货包裹测试数据
     */
    private List<EnhancedExcelExporter.RefundPackage> generateRefundPackageData() {
        EnhancedExcelExporter.RefundPackage refundPackage01 = new EnhancedExcelExporter.RefundPackage();
        refundPackage01.setId(1);
        refundPackage01.setPackageCode("P001");
        refundPackage01.setOrderCode("O001");
        refundPackage01.setReturnTrackingNumber("R001");
        refundPackage01.setCourierType(1);
        refundPackage01.setDamageLevel(1);
        refundPackage01.setReturnType(1);
        refundPackage01.setRemark("第一个退货包裹");
        refundPackage01.setCreator("admin");
        refundPackage01.setCreateTime(new Date());
        refundPackage01.setItems(Arrays.asList(
                new EnhancedExcelExporter.RefundPackage.RefundItem("I001", "iPhone 15 Pro", 1, 1, "屏幕损坏"),
                new EnhancedExcelExporter.RefundPackage.RefundItem("I002", "AirPods Pro", 2, 2, "音质问题")
        ));

        EnhancedExcelExporter.RefundPackage refundPackage02 = new EnhancedExcelExporter.RefundPackage();
        refundPackage02.setId(2);
        refundPackage02.setPackageCode("P002");
        refundPackage02.setOrderCode("O002");
        refundPackage02.setReturnTrackingNumber("R002");
        refundPackage02.setCourierType(2);
        refundPackage02.setDamageLevel(2);
        refundPackage02.setReturnType(2);
        refundPackage02.setRemark("第二个退货包裹");
        refundPackage02.setCreator("user001");
        refundPackage02.setCreateTime(new Date());
        refundPackage02.setItems(Collections.singletonList(
                new EnhancedExcelExporter.RefundPackage.RefundItem("I003", "MacBook Pro", 1, 3, "键盘故障")
        ));

        EnhancedExcelExporter.RefundPackage refundPackage03 = new EnhancedExcelExporter.RefundPackage();
        refundPackage03.setId(3);
        refundPackage03.setPackageCode("P003");
        refundPackage03.setOrderCode("O003");
        refundPackage03.setReturnTrackingNumber("R003");
        refundPackage03.setCourierType(3);
        refundPackage03.setDamageLevel(3);
        refundPackage03.setReturnType(3);
        refundPackage03.setRemark("第三个退货包裹");
        refundPackage03.setCreator("user002");
        refundPackage03.setCreateTime(new Date());
        refundPackage03.setItems(Collections.emptyList());

        return Arrays.asList(refundPackage01, refundPackage02, refundPackage03);
    }

    /**
     * 转换原始数据为导出DTO
     */
    private List<EnhancedExcelExporter.RefundPackageExportDTO> convertToExportDTO(List<EnhancedExcelExporter.RefundPackage> packages) {
        List<EnhancedExcelExporter.RefundPackageExportDTO> result = new ArrayList<>();

        for (EnhancedExcelExporter.RefundPackage pkg : packages) {
            // 包裹基本信息
            EnhancedExcelExporter.RefundPackageExportDTO baseDto = new EnhancedExcelExporter.RefundPackageExportDTO();
            baseDto.setPackageId(pkg.getId());
            baseDto.setPackageCode(pkg.getPackageCode());
            baseDto.setOrderCode(pkg.getOrderCode());
            baseDto.setReturnTrackingNumber(pkg.getReturnTrackingNumber());
            baseDto.setCourierType(pkg.getCourierType());
            baseDto.setPackageDamageLevel(pkg.getDamageLevel());
            baseDto.setReturnType(pkg.getReturnType());
            baseDto.setRemark(pkg.getRemark());
            baseDto.setCreator(pkg.getCreator());
            baseDto.setCreateTime(pkg.getCreateTime());

            // 添加货品行
            for (EnhancedExcelExporter.RefundPackage.RefundItem item : pkg.getItems()) {
                EnhancedExcelExporter.RefundPackageExportDTO itemDto = new EnhancedExcelExporter.RefundPackageExportDTO();
                // 复制包裹信息
                BeanUtils.copyProperties(baseDto, itemDto);

                // 设置货品信息
                itemDto.setItemCode(item.getItemCode());
                itemDto.setItemName(item.getItemName());
                itemDto.setItemQuantity(item.getQuantity());
                itemDto.setItemDamageLevel(item.getDamageLevel());
                itemDto.setItemRemark(item.getRemark());

                result.add(itemDto);
            }

            // 如果没有货品，添加空行
            if (pkg.getItems().isEmpty()) {
                result.add(baseDto);
            }
        }

        return result;
    }

    /**
     * 更新任务状态到Redis（双键存储保证兼容性）
     *
     * <p>该方法同时更新任务列表和状态查询键，确保任务管理的完整性和一致性。</p>
     *
     * @param downloadDto 任务对象
     * @param statusKey 状态查询键
     * @since 1.0
     */
    private void updateTaskStatus(DownloadDto downloadDto, String statusKey) {
        try {
            // 更新任务列表（用于/task/list接口）
            downloadUtil.updateDownloadList(downloadDto);
            // 更新状态查询键（用于/task/status/{taskId}接口）
            redisUtil.setObject(statusKey, downloadDto, 60 * 60 * 24);
            log.debug("任务状态更新成功，任务ID: {}, 状态: {}, 进度: {}%",
                downloadDto.getUuid(), downloadDto.getExportStatus(), downloadDto.getProgress());
        } catch (Exception e) {
            log.error("更新任务状态失败，任务ID: {}", downloadDto.getUuid(), e);
        }
    }

    /**
     * 获取导出数据源
     *
     * <p>根据账户和类型获取需要导出的数据。当前实现返回示例数据，
     * 实际使用时可以根据业务需求查询数据库获取真实数据。</p>
     *
     * <p>扩展建议：</p>
     * <ul>
     *   <li>支持分页查询处理大数据量</li>
     *   <li>支持查询条件过滤</li>
     *   <li>支持不同数据源类型</li>
     *   <li>支持数据权限控制</li>
     * </ul>
     *
     * @param account 账户信息，用于数据权限控制
     * @param type Excel库类型，可能影响数据格式
     * @return 导出数据列表
     * @since 1.0
     */
    private List<ExcelDataModel> getExportData(String account, String type) {
        // TODO: 这里可以根据实际业务需求从数据库查询真实数据
        // 例如：
        // - 根据account获取用户有权限访问的数据
        // - 根据type决定导出不同的数据集
        // - 支持分页处理大数据量
        // - 添加查询条件和过滤功能

        log.info("为账户 {} 获取 {} 类型的导出数据", account, type);

        // 当前返回示例数据，实际项目中应该查询数据库
        List<ExcelDataModel> dataList = generateSampleData();

        // 可以根据account添加更多个性化数据
        if ("test".equals(account)) {
            // 为测试账户添加更多数据
            for (int i = 0; i < 10; i++) {
                dataList.add(ExcelDataModel.builder()
                        .id(IdUtil.fastSimpleUUID())
                        .name("测试用户" + (i + 1))
                        .birthday(DateUtil.offsetDay(DateUtil.date(), -i))
                        .age(20 + i)
                        .createTime(new Date())
                        .updateTime(new Date())
                        .build());
            }
        }

        log.info("获取到 {} 条导出数据", dataList.size());
        return dataList;
    }

    /**
     * 生成Excel文件到下载目录
     *
     * <p>根据数据和配置生成Excel文件，支持多种Excel库，
     * 文件保存到安全的下载目录中供客户端下载。</p>
     *
     * @param dataList 导出数据列表
     * @param fileName 文件名
     * @param type Excel库类型
     * @param excelService Excel服务实现
     * @return 生成文件的完整路径
     * @throws Exception 文件生成异常
     * @since 1.0
     */
    private String generateExcelFile(List<ExcelDataModel> dataList, String fileName,
                                   String type, ExcelService excelService) throws Exception {
        // 使用配置的下载目录，而不是系统临时目录
        // TODO: 可以从配置文件中读取下载目录路径
        String downloadDir = System.getProperty("user.dir") + "/downloads/";

        // 确保下载目录存在
        java.io.File dir = new java.io.File(downloadDir);
        if (!dir.exists()) {
            dir.mkdirs();
        }

        String filePath = downloadDir + fileName;

        try {
            // 根据不同的Excel库类型生成文件
            switch (type.toLowerCase()) {
                case "easyexcel":
                    EasyExcel.write(filePath, ExcelDataModel.class)
                            .sheet("数据导出")
                            .doWrite(dataList);
                    break;
                case "easypoi":
                case "hutool":
                default:
                    // 对于其他类型，可以调用对应的服务实现
                    // 这里暂时使用EasyExcel作为默认实现
                    EasyExcel.write(filePath, ExcelDataModel.class)
                            .sheet("数据导出")
                            .doWrite(dataList);
                    break;
            }

            
            log.info("Excel文件生成成功：{}", filePath);
            return filePath;
        } catch (Exception e) {
            log.error("生成Excel文件失败：{}", filePath, e);
            throw new Exception("生成Excel文件失败：" + e.getMessage(), e);
        }
    }
    
    /**
     * 从所有可能的数据源查找任务信息
     * 
     * @param taskId 任务ID
     * @return 任务信息，如果没找到返回null
     */
    private DownloadDto findTaskFromAllSources(String taskId) {
        try {
            // 常见的账户名列表，可以根据实际情况调整
            String[] commonAccounts = {"test", "admin", "user", "default"};
            
            // 遍历可能的账户和模型组合
            for (String account : commonAccounts) {
                for (int model = 1; model <= 4; model++) {
                    List<DownloadDto> taskList = downloadUtil.getDownloadList(account, model);
                    for (DownloadDto task : taskList) {
                        if (taskId.equals(task.getUuid())) {
                            log.info("在账户 {} 的模型 {} 任务列表中找到任务: {}", account, model, taskId);
                            return task;
                        }
                    }
                }
            }
            
            log.warn("在所有常见账户的任务列表中未找到任务: {}", taskId);
            return null;
        } catch (Exception e) {
            log.error("从任务列表查找任务失败，任务ID: {}", taskId, e);
            return null;
        }
    }
    
    /**
     * 检查当前的防重复提交锁状态
     * 
     * @return 锁状态信息
     */
    private Map<String, Object> checkLockKeys() {
        Map<String, Object> lockInfo = new HashMap<>();
        
        try {
            // 检查常见的锁键
            String[] commonAccounts = {"test", "admin", "user", "default"};
            
            for (String account : commonAccounts) {
                String uploadLockKey = "Upload_" + account;
                String exportLockKey = "Export_" + account;
                String downloadLockKey = "Download_" + account;
                
                lockInfo.put(uploadLockKey, redisUtil.hasKey(uploadLockKey));
                lockInfo.put(exportLockKey, redisUtil.hasKey(exportLockKey));
                lockInfo.put(downloadLockKey, redisUtil.hasKey(downloadLockKey));
            }
            
            return lockInfo;
        } catch (Exception e) {
            log.error("检查锁键状态失败", e);
            lockInfo.put("error", e.getMessage());
            return lockInfo;
        }
    }
}
